package jdos.ints;

import jdos.Dosbox;
import jdos.cpu.CPU;
import jdos.cpu.CPU_Regs;
import jdos.cpu.Callback;
import jdos.cpu.Paging;
import jdos.dos.Dos_tables;
import jdos.hardware.DMA;
import jdos.hardware.IO;
import jdos.hardware.Memory;
import jdos.hardware.VGA_memory.VRAM;
import jdos.misc.Log;
import jdos.types.LogSeverities;
import jdos.types.LogTypes;
import jdos.util.IntRef;
import jdos.util.LongRef;

public class EMS {

    static final private int EMM_PAGES = 256; //16k byte page
    
	final public class ERAM {
		public static int[] direct = new int[EMM_PAGES * 16 * 1024];

		public static /*Bit8u*/short readb(/*HostPt*/int address) {
			return (short) ((direct[(address >>> 2)] >>> ((address & 0x3) << 3)) & 0xFF);
		}
	    public static /*Bit32u*/int readd(/*HostPt*/int address) {
            return direct[address >>> 2];
	    }

		public static void writeb(/*HostPt*/int address, /*Bit8u*/ int value) {
			int off = (address & 0x3) << 3;
			int[] local = direct;
			int mask = ~(0xFF << off);
			int index = (address >>> 2);
			int val = local[index] & mask | (value & 0xFF) << off;
			local[index] = val;
		}

	}

    static final private int EMM_PAGEFRAME = 0xE000;

    static final private int EMM_VERSION = 0x40;


    /* EMM errors */
    static final private int EMM_NO_ERROR = 0x00;
    static final private int EMM_FUNC_NOSUP = 0x84;

    static int[] offset = new int[4];

	public static int readb(/*PhysPt*/int addr) {
		int page = addr / (16 * 1024);
		addr = addr & (16 * 1024 - 1);
		return ERAM.readb(offset[page] + addr);
	}

	public static void writeb(/*PhysPt*/int addr, /*Bitu*/int val) {
		int page = addr / (16 * 1024);
		addr = addr & (16 * 1024 - 1);
		ERAM.writeb(offset[page] + addr, val);
	}

	private static /*Bit8u*/short EMM_MapPage(/*Bitu*/int phys_page, /*Bit16u*/int handle, /*Bit16u*/int log_page) {
		int pb = pageBegin[handle-1];
		offset[phys_page] = (pb + log_page) * (16 * 1024);
		return EMM_NO_ERROR;
	}

	static int index = 1;
	static int[] pageBegin = new int[8];
	
	private static Callback.Handler INT67_Handler = new Callback.Handler() {
        public String getName() {
            return "EMS.INT67_Handler";
        }

		public /*Bitu*/int call() {
            /*Bitu*/int i;
            switch (CPU_Regs.reg_eax.high()) {
            case 0x40:		/* Get Status */
                CPU_Regs.reg_eax.high(EMM_NO_ERROR);
                break;
            case 0x41:		/* Get PageFrame Segment */
                CPU_Regs.reg_ebx.word(EMM_PAGEFRAME);
                CPU_Regs.reg_eax.high(EMM_NO_ERROR);
                break;
            case 0x42:		/* Get number of pages */
                CPU_Regs.reg_edx.word(EMM_PAGES);//total x*16k
                CPU_Regs.reg_ebx.word(EMM_PAGES-pageBegin[index]);//free x*16k
                CPU_Regs.reg_eax.high(EMM_NO_ERROR);
                break;
			case 0x43: /* Get Handle and Allocate Pages */
			{
				CPU_Regs.reg_eax.high(EMM_NO_ERROR);
				int pages = CPU_Regs.reg_ebx.word();
				index++;
				int size = 0;
				pageBegin[index] = size + pages;
				CPU_Regs.reg_edx.word(index);
				break;
			}
            case 0x44:		/* Map Expanded Memory Page */
                CPU_Regs.reg_eax.high(EMM_MapPage(CPU_Regs.reg_eax.low(),CPU_Regs.reg_edx.word(),CPU_Regs.reg_ebx.word()));
                break;
            case 0x45:		/* Release handle and free pages */
				index--;
                CPU_Regs.reg_eax.high(EMM_NO_ERROR);
                break;
            case 0x46:		/* Get EMM Version */
                CPU_Regs.reg_eax.high(EMM_NO_ERROR);
                CPU_Regs.reg_eax.low(EMM_VERSION);
                break;
            case 0x47:		/* Save Page Map */
            	CPU_Regs.reg_eax.high(EMM_FUNC_NOSUP);
                //CPU_Regs.reg_eax.high(EMM_SavePageMap(CPU_Regs.reg_edx.word()));
                break;
            case 0x48:		/* Restore Page Map */
            	CPU_Regs.reg_eax.high(EMM_FUNC_NOSUP);
                //CPU_Regs.reg_eax.high(EMM_RestorePageMap(CPU_Regs.reg_edx.word()));
                break;
            
            default:
                if (Log.level<=LogSeverities.LOG_ERROR) Log.log(LogTypes.LOG_MISC,LogSeverities.LOG_ERROR,"EMS:Call "+Integer.toString(CPU_Regs.reg_eax.high(), 16)+" not supported");
                CPU_Regs.reg_eax.high(EMM_FUNC_NOSUP);
                break;
            }
            return Callback.CBRET_NONE;
        }
    };

    //private DOS_Device emm_device;
    /* location in protected unfreeable memory where the ems name and callback are
     * stored  32 bytes.*/
    private /*RealPt*/int old67_pointer;
    /*Bitu*/int call_int67;

    EMS() {
//if(true)return;
        Bios.BIOS_ZeroExtendedSize(true);
        int ems_baseseg = 0xd000;
        //if (ems_baseseg==0) ems_baseseg=Dos_tables.DOS_GetMemory(2);	//We have 32 bytes

        /* Add a little hack so it appears that there is an actual ems device installed */
        String emsname="EMMXXXX0";
        Memory.MEM_BlockWrite(Memory.PhysMake(ems_baseseg,0xa),emsname,emsname.length()+1);

        call_int67=Callback.CALLBACK_Allocate();
        Callback.CALLBACK_Setup(call_int67,INT67_Handler,Callback.CB_IRET,Memory.PhysMake(ems_baseseg,4),"Int 67 ems");
        IntRef o = new IntRef(old67_pointer);
        Memory.RealSetVec(0x67,Memory.RealMake(ems_baseseg,4),o);
        old67_pointer = o.value;

    }

    static EMS test;

    public static void EMS_Init() {
    	//if(true)return;
        test = new EMS();
    }
}
